/*
 * This file contains code concerning the ingame screen / application state
 * in general. Functions handling the execution and queuing of Actions can be
 * found in the logic module. Draw functions for various game elements can
 * be found in their respective modules, e.g. map_display in map.c.
 */
#include <assert.h>
#include <allegro.h>
#include "game.h"
#include "main.h"
#include "world/map.h"
#include "draw/map_display.h"
#include "draw/gui.h"
#include "network.h"
#include "logic.h"
#include "generator/dungeon.h"
#include "util/log.h"
#include "draw/chat.h"
#include "world/items.h"
#include "resources/resources.h"
#include "draw/colors.h"
#include "util/mt.h"
#include "common/common_game.h"

#define MAX_CHAT_LENGTH 256
#define KEY_ATTACK_CLOSE KEY_D
#define KEY_ATTACK_RANGE KEY_S

#if TOWERHACK_DEBUG
MapDisplay display; // allow export for outside access
#else
static MapDisplay display;
#endif
static Hud* hud = NULL;
static CtoSmessage netmessage; // For communications to server
static StoCmessage received;  // For communications to server
Game the_game; // TODO: kill this obnoxious global variable
static enum { INPUT_GAME, INPUT_CHOICE, INPUT_CHAT } input_state;
int choice_found; // Offer this item for INPUT_CHOICE
int choice_select;

int game_my_player;

void game_damage_handler (int time, Coord where, int attacker, int victim, int weapon, int damage);

void game_init ()
{
	input_state = INPUT_GAME;
	clear_keybuf();

	common_game_init ();
	
	// let's make sure we are now in control of an existing character
	assert((game_my_player >= 0) && (game_my_player < the_game.map.actor.count));
	
	map_display_init (&display, &the_game.map);
	
	assert(hud == NULL);
	hud = hud_create (map_get_actor (&the_game.map, game_my_player));
	
	choice_found = 0;
	choice_select = 0;
	
	chat_init ();
	damage_handler = &game_damage_handler; // Set handler for logic
	
	#if TOWERHACK_DEBUG
	show_mouse(screen);
	#endif
}

// Creates a map, maybe?
void game_seed (unsigned long seed, int n_characters)
{
	common_game_seed (seed, n_characters);
}

static void send_chat(const char* text) 
{
	CtoSmessage msg;
	msg.type = CtoS_CHAT;
	strcpy (msg.line, text);
	send_CtoSmessage(&msg);
}

void send_action (const Action* a)
{
	CtoSmessage msg;
	msg.type = CtoS_INGAME_ACTION;
	msg.action = *a;
	send_CtoSmessage(&msg);
}

void get_kbinput (int* key_press, int* key_release)
{
	static int key_memory [4] = {0,0,0,0}; // ↑↓←→ D S (ENTER)
	
	if ((map_get_actor (&the_game.map, game_my_player)->action.type != ACTION_MOVE) &&
	    (map_get_actor (&the_game.map, game_my_player)->intention.type != ACTION_MOVE))
			key_memory[0] = 0;
	
	*key_press = *key_release = 0;
	if ((key_memory[0] != KEY_UP)    && key[KEY_UP]    && !key[KEY_DOWN])  { *key_press = KEY_UP;    key_memory[0] = KEY_UP; return; }
	if ((key_memory[0] != KEY_DOWN)  && key[KEY_DOWN]  && !key[KEY_UP])    { *key_press = KEY_DOWN;  key_memory[0] = KEY_DOWN; return; }
	if ((key_memory[0] != KEY_LEFT)  && key[KEY_LEFT]  && !key[KEY_RIGHT]) { *key_press = KEY_LEFT;  key_memory[0] = KEY_LEFT; return; }
	if ((key_memory[0] != KEY_RIGHT) && key[KEY_RIGHT] && !key[KEY_LEFT])  { *key_press = KEY_RIGHT; key_memory[0] = KEY_RIGHT; return; }
	
	if ((key_memory[0] == KEY_UP)    && !key[KEY_UP])    { *key_release = KEY_UP;    key_memory[0] = 0; return; }
	if ((key_memory[0] == KEY_DOWN)  && !key[KEY_DOWN])  { *key_release = KEY_DOWN;  key_memory[0] = 0; return; }
	if ((key_memory[0] == KEY_LEFT)  && !key[KEY_LEFT])  { *key_release = KEY_LEFT;  key_memory[0] = 0; return; }
	if ((key_memory[0] == KEY_RIGHT) && !key[KEY_RIGHT]) { *key_release = KEY_RIGHT; key_memory[0] = 0; return; }
	
	if (!key_memory[1] && key[KEY_ATTACK_CLOSE]) { *key_press = KEY_ATTACK_CLOSE; key_memory[1] = 1; return; }
	if (!key_memory[2] && key[KEY_ATTACK_RANGE]) { *key_press = KEY_ATTACK_RANGE; key_memory[2] = 1; return; }
	if (!key_memory[3] && key[KEY_ENTER])        { *key_press = KEY_ENTER;        key_memory[3] = 1; return; }
	
	if (key_memory[1] && !key[KEY_ATTACK_CLOSE]) { key_memory[1] = 0; return; }
	if (key_memory[2] && !key[KEY_ATTACK_RANGE]) { key_memory[2] = 0; return; }
	if (key_memory[3] && !key[KEY_ENTER])        { key_memory[3] = 0; return; }
}

void game_update ()
{
	Action a = NO_ACTION(-1);
	Actor* victim = NULL;
	
	receive_StoCmessage (&received);
	while (received.type != StoC_NOMSG) {
		switch (received.type) {
			
		case StoC_INGAME_ACTION:
			assert((received.action.actor >= 0) && (received.action.actor < MAX_PLAYERS));
			if (received.action.actor != game_my_player) {
				logic_intend (&received.action);
			} else {
				action_free (&received.action);
			}
			break;
			
		case StoC_INGAME_DAMAGE:
			assert((received.damage.victim >= 0) && (received.damage.victim < the_game.map.actor.count));
			victim = map_get_actor (&the_game.map, received.damage.victim);
			victim->hp = received.damage.hp;
			
			log_debug ("Deal %d damage from %d to %d. Victim hp is now %d.",
			           received.damage.damage, received.damage.attacker,
			           received.damage.victim, received.damage.hp);
			break;
			
		case StoC_INGAME_DIE:
			assert((received.damage.victim >= 0) && (received.damage.victim < the_game.map.actor.count));
			victim = map_get_actor (&the_game.map, received.damage.victim);
			victim->action = action_alloc_die (received.damage.victim, the_game.game_time);
			break;
			
		case StoC_INGAME_SETCHARACTER:
			assert((received.who >= 0) && (received.who < the_game.map.actor.count));
			
			Actor* actor = map_get_actor (&the_game.map, received.who);
			assert(actor->type = ACTOR_CHARACTER);
			
			actor->level = received.level;
			actor->hp = received.hp;
			actor->hp_max = received.hp;
			memcpy(&actor->character->inventory, &received.inventory, sizeof(Inventory));
			break;

		case StoC_CHAT:
			chat_receive(received.line);
			break;
			
		default:
			log_fatal ("Received unidentified message type %d from server.",
			           received.type);
			break;
			
		}
		
		receive_StoCmessage (&received);
	}
	
	Actor* controlled = map_get_actor (&the_game.map, game_my_player);
	char* line;
	int key_press, key_release;
	
	switch (input_state)
	{
		
	case INPUT_GAME:
		get_kbinput (&key_press, &key_release);
	
		int time = time_asap(controlled->id);
		if ((key_press == KEY_ATTACK_CLOSE) && (controlled->character->inventory.slot[INV_WEAPON_CLOSE] != ITEM_NOTHING))
			a = action_alloc_closeattack (game_my_player, time, controlled->character->inventory.slot[INV_WEAPON_CLOSE]);
		else if ((key_press == KEY_ATTACK_RANGE) && (controlled->character->inventory.slot[INV_WEAPON_RANGE] != ITEM_NOTHING))
			a = action_alloc_rangeattack (game_my_player, time, controlled->character->inventory.slot[INV_WEAPON_RANGE]);
		else if (key_press == KEY_LEFT)  a = action_alloc_turn (game_my_player, time, DIR_LEFT);
		else if (key_press == KEY_RIGHT) a = action_alloc_turn (game_my_player, time, DIR_RIGHT);
		else if (key_press == KEY_UP)    a = action_alloc_turn (game_my_player, time, DIR_UP);
		else if (key_press == KEY_DOWN)  a = action_alloc_turn (game_my_player, time, DIR_DOWN);
		else if ((key_release == KEY_LEFT) || (key_release == KEY_RIGHT) || (key_release == KEY_UP) || (key_release == KEY_DOWN))
			a = action_alloc_halt (game_my_player, time);

		if ((a.type != ACTION_NONE) && (action_is_valid (&the_game.map, &a))) {
			logic_intend (&a);
			send_action (&a);
		} else {
			action_free (&a);
		}
	
		if (keypressed() && key[KEY_ENTER]) {
			input_state = INPUT_CHAT; 
			clear_keybuf();
		}
		break;
		
	case INPUT_CHAT:
		if ((line = chat_input()) != NULL) {
			input_state = INPUT_GAME;
			key[KEY_ENTER] = 0;
			if (strlen(line) > 0) {
				send_chat(line);
			}
		}
		break;
		
	case INPUT_CHOICE:
		if (!keypressed()) break;
		if ((key[KEY_LEFT]) || (key[KEY_RIGHT])) choice_select = !choice_select;
		if (key[KEY_S]) {
			netmessage.type = CtoS_INGAME_SETINVENTORY;
			netmessage.character = game_my_player;
			memcpy (&netmessage.inventory, &controlled->character->inventory, sizeof(Inventory));
			if (choice_select)
				netmessage.inventory.slot[items[choice_found].type - ITEM_WEAPON_CLOSE] = choice_found;
			send_CtoSmessage (&netmessage);
			input_state = INPUT_GAME;
		}
		clear_keybuf();
		break;
		
	}

	if (the_game.game_time % (FPS/10) == 0) display.anim_frame++;
	logic_update (&the_game.map);
	hud_update (hud);
	chat_update_messages ();
	for (int i = 0; i < MAX_EFFECTS; i++) effect_update (&display.effect[i]);
}

void display_choice () 
{
	Actor* controlled = map_get_actor (&the_game.map, game_my_player);
	
	rectfill(buffer, DISPLAY_WIDTH/2-110, DISPLAY_HEIGHT/2-55, DISPLAY_WIDTH/2-1, DISPLAY_HEIGHT/2+54, choice_select ? CHOICEWIN_COL : CHOICEWIN_SELECT_COL);
	rectfill(buffer, DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2-55, DISPLAY_WIDTH/2+109, DISPLAY_HEIGHT/2+54, choice_select ? CHOICEWIN_SELECT_COL : CHOICEWIN_COL);
	if (!choice_select) {
		rectfill(buffer, DISPLAY_WIDTH/2-105, DISPLAY_HEIGHT/2-50, DISPLAY_WIDTH/2-6, DISPLAY_HEIGHT/2+49, CHOICEWIN_SELECT_BG_COL);
		rect (buffer, DISPLAY_WIDTH/2-55-9, DISPLAY_HEIGHT/2-9, DISPLAY_WIDTH/2-55+8, DISPLAY_HEIGHT/2+8, CHOICEWIN_SELECT_COL);
	} else {
		rectfill(buffer, DISPLAY_WIDTH/2+5, DISPLAY_HEIGHT/2-50, DISPLAY_WIDTH/2+104, DISPLAY_HEIGHT/2+49, CHOICEWIN_SELECT_BG_COL);
		rect (buffer, DISPLAY_WIDTH/2+55-9, DISPLAY_HEIGHT/2-9, DISPLAY_WIDTH/2+55+8, DISPLAY_HEIGHT/2+8, CHOICEWIN_SELECT_COL);
	}
	textprintf_centre_ex(buffer, font, DISPLAY_WIDTH/2-55, DISPLAY_HEIGHT/2-45, CHOICEWIN_SELECT_COL, CHOICEWIN_COL, "Keep");
	
	int old_item = controlled->character->inventory.slot[items[choice_found].type - ITEM_WEAPON_CLOSE];
	textprintf_right_ex(buffer, font, DISPLAY_WIDTH/2-5, DISPLAY_HEIGHT/2-35, CHOICEWIN_SELECT_COL, CHOICEWIN_COL, "%s", items[old_item].name);
	textprintf_centre_ex(buffer, font, DISPLAY_WIDTH/2+55, DISPLAY_HEIGHT/2-45, CHOICEWIN_SELECT_COL, CHOICEWIN_COL, "Pick up");
	textprintf_ex(buffer, font, DISPLAY_WIDTH/2+5, DISPLAY_HEIGHT/2-35, CHOICEWIN_SELECT_COL, CHOICEWIN_COL, "%s", items[choice_found].name);
	masked_blit(bmp.icons, buffer, items[old_item].icon_x*16, items[old_item].icon_y*16, DISPLAY_WIDTH/2-55-8, DISPLAY_HEIGHT/2-8, 16, 16);
	masked_blit(bmp.icons, buffer, items[choice_found].icon_x*16, items[choice_found].icon_y*16, DISPLAY_WIDTH/2+55-8, DISPLAY_HEIGHT/2-8, 16, 16);
}

void game_draw ()
{
	acquire_bitmap (buffer);
	clear_bitmap (buffer);

	Coord player_pos = actor_pxpos (map_get_actor (&the_game.map, game_my_player));
	int layer = player_pos.z; // map layer to display
	map_display_set_observer (&display, player_pos);
	map_display_bg (&display, buffer);
	map_display_objects (&display, buffer);
	for (int i = 0; i < the_game.map.actor.count; i++) {
		Actor* actor = map_get_actor (&the_game.map, i);
		if (actor->coord.z == layer) {
			if (actor->type == ACTOR_CHARACTER) {
				map_display_character (&display, actor);
			} else
			if (actor->type == ACTOR_MONSTER) {
				if (actor->monster->type != MONSTER_UNDEFINED) {
					map_display_monster (&display, actor);
				}
			}
		}
	}
	map_display_fg (&display, buffer);
	chat_display_messages ();
	hud_draw (hud);

	if (input_state == INPUT_CHAT) {
		chat_display_input ();
	} 
	else if (input_state == INPUT_CHOICE) {
		display_choice ();
	}
	release_bitmap (buffer);
}

void game_cleanup ()
{
	hud_free(hud);
	map_display_cleanup (&display);
	
	hud = NULL;
	
	common_game_cleanup ();
	input_state = INPUT_GAME;
}

/**
 * attacker - actor handle
 * victim - actor handle
 * weapon - item handle
 * damage - damage points (integer)
 */
void game_damage_handler (int time, Coord where, int attacker, int victim, int weapon, int damage)
{
	log_debug ("DAMAGE! At time %d, location (%d,%d,%d), %d hit %d with %s, "
	           "causing %d points of damage.\n", time, where.x, where.y, where.z,
	           attacker, victim, items[weapon].name, damage);
	// map_display_add_effect (&display, effect_init_damage (damage, where));
}

